

/**
* @brief BOOT启动文件
* @author chenjl
* @date 2019.07.15
*/

#include "stm32f4xx.h"


/**
*   @brief FLASH布局
*                 -------------------
*  0x0800_0000   |    boot_loader   |
*                |------------------|
*  0x0800_8000   |        OS        |
*                |------------------|
*  0x0802_0000   |       APP1       |
*  0x0804_0000   |       APP2       |
*  @details 设计原则为：
*  > 1. 第一个16K的SECTOR用于BOOT_LOADER程序存储，为其保留32K空间
*  > 2. 第三个16K的SECTOR用于OS，为其保留32k空间
*  > 3. APP程序使用0x0802_0000后的每个128K空间
*  > 4. 0x0801_0000开始的64K用于保存各项参数值
*/

/**
* 系统使用外部晶振作为时钟，晶振频率 HSE_VALUE = 12M
* 配置后，系统主频 168M
*/

/*系统参数配置区域*/
/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
#define PLL_M      12
#define PLL_N      336

/*下列参数是默认参数*/
/* SYSCLK = PLL_VCO / PLL_P */
#define PLL_P      2
/* USB OTG FS, SDIO and RNG Clock =  PLL_VCO / PLLQ */
#define PLL_Q      7

#define UART1PBCLOCK    (PLL_N*1000000/(PLL_P*2))

static void SetSysClock(void);
static void uart1_init(void);
static void usart_copyright(void);
static void usart_printstr(char *msg);
void BurnMain(void);

char    bootBurnMode = 0;

typedef void (*OsEntry)(void);
#define OS_FLASH_ADDR   (0x08008000)

/**
* @brief 初始化系统基础环境，针对STM32F4XX控制器，主要是初始化系统时钟
*/
void SystemInit(void)
{
    /*使能内部高速时钟*/
    RCC->CR |= (uint32_t)0x00000001;
    /*选择HSI作为系统时钟，不分频*/
    RCC->CFGR = 0x00000000;
    /*禁止主PLL,系统时钟安全关闭，HSE振荡器关闭*/
    RCC->CR &= (uint32_t)0xFEF6FFFF;
    /*hsi作振荡器时钟，*/
    RCC->PLLCFGR = 0x24003010;
    /*不旁路HSI*/
    RCC->CR &= (uint32_t)0xFFFBFFFF;

    /*清除所有时钟中断*/
    RCC->CIR = 0x00000000;
    
    /*=============================*/
    /*设置系统时钟*/    
    SetSysClock();
    
}

/**
* @breif 设置系统时钟
*/
static void SetSysClock(void)
{
    #define HSE_STARTUP_TIMEOUT    ((uint16_t)0x05000)
    
    __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
  
    /*打开HSE振荡器*/
    RCC->CR |= ((uint32_t)RCC_CR_HSEON);
    
    /*等待HSE准备就绪或超时*/
    do
    {
        HSEStatus = RCC->CR & RCC_CR_HSERDY;
        StartUpCounter++;
    } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

    if((RCC->CR & RCC_CR_HSERDY) != RESET)
    {
        /*HSE外部时钟准备就绪*/
        RCC->APB1ENR |= RCC_APB1ENR_PWREN;
        PWR->CR |= PWR_CR_VOS;
        
        /* HCLK = SYSCLK / 1*/
        RCC->CFGR |= RCC_CFGR_HPRE_DIV1;
          
        /* PCLK2 = HCLK / 2*/
        RCC->CFGR |= RCC_CFGR_PPRE2_DIV2;
        
        /* PCLK1 = HCLK / 4*/
        RCC->CFGR |= RCC_CFGR_PPRE1_DIV4;
        
        /* Configure the main PLL */
        RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
                   (RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);

        /* Enable the main PLL */
        RCC->CR |= RCC_CR_PLLON;

        /* Wait till the main PLL is ready */
        while((RCC->CR & RCC_CR_PLLRDY) == 0)
        {
        }
        /* Configure Flash prefetch, Instruction cache, Data cache and wait state */
        FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;

        /* Select the main PLL as system clock source */
        RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
        RCC->CFGR |= RCC_CFGR_SW_PLL;

        /* Wait till the main PLL is used as system clock source */
        while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL)
        {
        }
    }
}

/**
* @brief 时钟已经准备就绪，准备初始化串口1
* 1. 显示基础信息
* 2. 判断是否进入固化模式；是则开始进入固化
* 3. 否则直接跳到OS运行
*/

int main(void)
{
    OsEntry os_route;
    //初始串口1收发
    uart1_init();
    
    //串口输出提示信息，并同时判断是否有固化模式指令
    usart_copyright();
    
    if(bootBurnMode != 0)
    {
        //进入固化模式
        usart_printstr("Burnnng..\r\n");
        BurnMain();
    }    
    //跳转到OS
    usart_printstr("Running..\r\n");
    //禁止掉USART1
    USART1->CR1 = 0;
    
    //设置中断栈空间
    SCB->VTOR = OS_FLASH_ADDR;
    __set_MSP(*(long*)OS_FLASH_ADDR);
    os_route = (OsEntry)(*(long*)(OS_FLASH_ADDR+4));
    os_route();
}

static void uart1_init(void)
{
    uint32_t integerdivider = 0x00;
    uint32_t fractionaldivider = 0x00;
    uint32_t tmpreg ;
    
    /*串口1使用PA9发送，PA10接收*/
    /*串口1的默认配置参数为 115200,8,1,N*/
    RCC->AHB1ENR |= 0x1;            //使能GPIOA时钟
    RCC->APB2ENR |= 0x10;           //使能UART1时钟
    
    /*配置GPIO引脚*/
    GPIOA->MODER &= ~(0xf << (2*9));
    GPIOA->MODER |= (0xa << (2*9));     //PA9,PA10复用功能模式
    
    GPIOA->OSPEEDR &= ~(0xf << (2*9));  //引脚输出时钟
    
    GPIOA->OTYPER  &= ~(0x3 << 9);
    GPIOA->OTYPER  |= 0x1 << 10;        //PA9=PP,PA10=OD
    
    GPIOA->PUPDR   &= ~(0xf << (2*9));
    GPIOA->PUPDR   |= 0x1 << (2*9);     //PA9=上拉，PA10无上拉下拉
    
    /*打开引脚的复用功能*/
    GPIOA->AFR[1] &= ~(0xff<<4);
    GPIOA->AFR[1] |= 0x77<<4;           //复用功能选择UART1
    
    /*配置串口*/
    USART1->CR1 = 0xc;              //使能收发，同时停止UART1的时钟
    USART1->CR2 = 0;                //1个停止位
    USART1->CR3 = 0;
    
    integerdivider = ((25 * UART1PBCLOCK) / (4 * 115200));    
    tmpreg = (integerdivider / 100) << 4;
    fractionaldivider = integerdivider - (100 * (tmpreg >> 4));
    tmpreg |= ((((fractionaldivider * 16) + 50) / 100)) & ((uint8_t)0x0F);
    USART1->BRR = tmpreg;
    
    USART1->CR1 |= 0x2000;
    USART1->SR = 0;    
}

const char osBootCpyRight[] = 
    "             AnOs BootLoader         "
    "\r\nversion:0.1    date:2019.07"
    "\r\nTHIS is a bootloader for ANOS system."    
    "\r\ncopyright @2019 chenjl"
    "\r\nmail:cjl-0123@163.com"
    "\r\n\r\n";

/**
* @brief 显示BOOT信息，并判断是否需要进入固化模式
*/
static void usart_copyright(void)
{
    int idx = 0;
    int burnModeCnt = 0;
    while(osBootCpyRight[idx] != '\0')
    {
        while(0 == (USART1->SR & 0x80))
        {
            //发送缓存区不为空，等待，同时判断接收的数据
            if(USART1->SR & 0x20)
            {
                //有接收数据
                if(USART1->DR == 0x7e)
                {
                    burnModeCnt++;
                }
            }
        }
        //发送缓冲区为空时，发送一个字符
        USART1->DR = osBootCpyRight[idx++];
    }
    
    /*发送完成后，判断是否进入固化模式*/
    if(burnModeCnt >= 3)
    {
        bootBurnMode = 1;
    }
    else
    {
        bootBurnMode = 0;
    }
}

/**
* @brief 串口输出
*/
static void usart_printstr(char *msg)
{
    void putChar(u8 ch);
    while(*msg != '\0')
    {
        putChar(*msg++);
    }
}

u8  getUpdateChar(void)
{
    return USART1->DR;
}

u8  isCharPresent(void)
{
    return ((USART1->SR & 0x20)!=0);
}

void putChar(u8 ch)
{
    while(0 == (USART1->SR & 0x80))
    {
        //发送缓存区不为空，等待
    }
    USART1->DR = ch;
}
